#ifndef __BOUND_INL__
#define __BOUND_INL__
#include "bound.h"

template <class T, size_t Size>
Bound<T, Size>::Bound() {
    for (size_t i = 0; i < Size; ++i) {
        pMax.data[i] = std::numeric_limits<T>::max();
        pMin.data[i] = std::numeric_limits<T>::min();
    }
}

template <class T, size_t Size>
Bound<T, Size>::Bound(const detail::Vec<T, Size>& p1, const detail::Vec<T, Size>& p2) {
    for (size_t i = 0; i < Size; ++i) {
        pMin.data[i] = std::min(p1.data[i], p2.data[i]);
        pMax.data[i] = std::max(p1.data[i], p2.data[i]);
    }
}

template <class T, size_t Size>
detail::Vec<T, Size> Bound<T, Size>::diagonal() const {
    return pMax - pMin;
}

template <class T, size_t Size>
T Bound<T, Size>::volume() const {
    detail::Vec<T, Size> d = diagonal();
    for (int i = 1; i < Size; ++i) {
        d.data[0] *= d.data[i];
    }
    return d.data[0];
}

template <class T, size_t Size>
size_t Bound<T, Size>::maximumExtent() const {
    detail::Vec<T, Size> d = diagonal();
    auto it = std::max_element(std::begin(d.data), std::end(d.data));
    return it - std::begin(d.data);
}

template <class T, size_t Size>
template <class U>
auto Bound<T, Size>::lerp(const detail::Vec<U, Size>& t) 
-> detail::Vec<std::common_type_t<T, U>, Size> const 
{
    detail::Vec<std::common_type_t<T, U>, Size> result;
    for (int i = 0; i < Size; ++i) {
        result.data[i] = ::lerp(pMin.data[i], pMax.data[i], t.data[i]);
    }
    return result;
}

template <class T, size_t Size>
detail::Vec<T, Size> Bound<T, Size>::offset(const detail::Vec<T, Size>& p) const {
    detail::Vec<T, Size> o = p - pMin;
    for (int i = 0; i < Size; ++i) {
        if (pMax.data[i] > pMin.data[i]) {
            o.data[i] /= pMax.data[i] - pMin.data[i];
        }
    }
    return o;
}

template <class T, size_t Size>
void Bound<T, Size>::boundingSphere(detail::Vec<T, Size>* center, float* radius) const {
    *center = (pMin + pMax) / 2;
    *radius = inside(*center, *this) ? ::length(*center, pMax) : (T)0;
}

inline BoundIter::BoundIter(const Bound<int, 2>* bound, detail::Vec<int, 2>&& point)
    : mBound(bound), mCurrPoint(std::move(point)) { }

inline BoundIter::BoundIter(const Bound<int, 2>* bound)
    : mBound(bound), mCurrPoint(bound->pMin) { }

inline BoundIter& BoundIter::operator ++ () {
    advance();
    return *this;
}

inline BoundIter BoundIter::operator ++ (int) {
    BoundIter old = *this;
    advance();
    return old;
}

inline detail::Vec<int, 2>& BoundIter::operator * ()  {
    return mCurrPoint;
}

inline void BoundIter::advance() {
    if (mCurrPoint.x > mBound->pMax.x) {
        mCurrPoint.y += 1;
        mCurrPoint.x = mBound->pMin.x;
    }
    else {
        mCurrPoint.x += 1;
    }
}

template <class T, size_t Size>
Bound<T, Size> operator & (const Bound<T, Size>& bound1, const Bound<T, Size>& bound2) noexcept {
    detail::Vec<T, Size> max;
    detail::Vec<T, Size> min;
    for (int i = 0; i < Size; ++i) {
        max.data[i] = std::max(bound1.pMax.data[i], bound2.pMin.data[i]);
        min.data[i] = std::min(bound1.pMin.data[i], bound2.pMax.data[i]);
    }
    return Bound(max, min);
}

template <class T, size_t Size>
Bound<T, Size> intersect(const Bound<T, Size>& bound1, const Bound<T, Size> bound2) noexcept {
    Bound<T, Size> result;
    for (int i = 0; i < Size; ++i) {
        result.pMin.data[i] = std::max(bound1.pMin.data[i], bound1.pMin.data[i]);
        result.pMax.data[i] = std::min(bound1.pMax.data[i], bound1.pMax.data[i]);
    }
    return result;
}

template <class T, size_t Size>
bool overlaps(const Bound<T, Size>& bound1, const Bound<T, Size>& bound2) noexcept {
    static_assert(!std::is_integral_v<T>);
    bool isOverlaps = true;
    for (int i = 0; i < Size; ++i) {
        isOverlaps = isOverlaps && bound1.pMax.data[i] >= bound2.pMin.data[i] && bound1.pMin.data <= bound2.pMax.data[i];
    }
    return isOverlaps;
}

template <class T, size_t Size>
bool inside(const detail::Vec<T, Size>& point, const Bound<T, Size>& bound) noexcept {
    for (int i = 0; i < Size; ++i) {
        if (point.data[i] < bound.pMin.data[i] || point.data[i] > bound.pMax.data[i]) {
            return false;
        }
    }
    return true;
}

template <class T, size_t Size>
bool insideExclusive(const detail::Vec<T, Size>& point, const Bound<T, Size> bound) noexcept {
    for (int i = 0; i < Size; ++i) {
        if (point.data[i] < bound.pMin.data[i] || point.data[i] >= bound.Max.data[i]) {
            return false;
        }
    }
    return true;
}

template <class T, class U, size_t Size>
Bound<T, Size> expand(const Bound<T, Size>& bound, const U delta) noexcept {
    Bound<T, Size> result;
    for (int i = 0; i < Size; ++i) {
        result.pMax.data[i] = bound.pMax.data[i] + delta;
        result.pMin.data[i] = bound.pMin.data[i] - delta;
    }
    return result;
}

#endif